#define pexit(s) { fprintf( stderr, "%s\n", s ); exit(1); }
#include <vector>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <math.h>
#include "jerasure.h"
#include "reed_sol.h"
#include "galois.h"
using namespace std;

int oct_to_int(char *oct);
vector<string> read_directory( string dir );
unsigned int compute_checksum( unsigned char* buffer, unsigned int blocksize );

int main( int argc, char** argv )
{
	unsigned int k, m, w;
	unsigned int blocksize;
	unsigned int fs_orig, fs_tarred; //size of a file going into the archive, original and tar'd
	unsigned int fs_tar_file; //size of the tar file
	string dir;
	vector<string> files;
	vector<unsigned int> tarred_sizes;
	vector<unsigned int> checksum;
	string sys_command;
	unsigned char tar_buf[512];
	unsigned char *buffer;
	unsigned char *buffer2;
	char *dest;
	unsigned int i, l, j;
	FILE* fp;
	uint64_t val;
	unsigned int cursize;
	JER_Slices *slices;
	JER_Gen_T * gen;

	printf("\n");

	if( argc != 6 )
		pexit( "usage: ./personal_archiving M W blocksize dir pc\n" );

	m = atoi( argv[1] );
	w = atoi( argv[2] );
	blocksize = atoi( argv[3] );
	dir = string( argv[4] );
	dest = argv[5];

	if( w != 8 && w != 16 && w != 32){
		fprintf(stderr,"Error, w must be 8, 16, or 32\n");
		exit(1);
	}

	if( blocksize <= 0 || m <= 0 || w <= 0 )
		pexit( "Error: improper input parameters" );

	/* Open the specified directory and read in all files in it. Called 
	   recursively for getting files from subdirectories. Insert specified
	   directory at the beginning. */
	files = read_directory( dir );
	files.insert( files.begin(), dir );

	/* Create a tar file of all files in the specified directory. All archiving
	   will be done on this file. */
	printf("Tar'ing the input folder\n\n");
	sys_command = "tar -cf temp.tar " + dir;
	system( sys_command.c_str() );

	// Open tar-file, read header information for each file to determine filesize.
	fp = fopen( "temp.tar", "r" );
	printf("Listing tar file's contents (note: All files are zero-padded to a multiple of 512 bytes. Each file also contains 512 extra bytes of meta-data.)\n");
	fs_tar_file = 0;
	k = 0;
	while( !feof( fp ) ){

		//read the header for a file
		//fs_tarred is the total size of this file in the tar file (including the 512 byte header); the "overall filesize"
		fs_tarred = fread( tar_buf, 1, 512, fp );

		if( !feof( fp ) && tar_buf[0] != 0 ){

			//read the original size of the file
			fs_orig = oct_to_int((char *)tar_buf+124);
			
			//in the tar file, non-folders are padded to a multiple of 512 bytes
			//folders have no data after the 512 header, and their original size is 0
			if( fs_orig != 0 ){
				fs_tarred += ( ( fs_orig - 1 ) / 512 + 1 ) * 512;
			}

			//compute how many blocks we will make out of this file
			//k += ceil( fs_tarred/blocksize)
			k += ( fs_tarred - 1 ) / blocksize + 1;
			tarred_sizes.push_back( fs_tarred );
			fs_tar_file += fs_tarred;

			printf( "%5s%-20s Original size: %-10d Size in .tar: %-10d\n","",tar_buf,fs_orig, fs_tarred);

			fseek( fp, fs_tarred-512, SEEK_CUR );
		}
	}
	printf("\n");

	//the user specified a k value
	//we will break the tar-file (size fs_tar_file) into k blocks.
	//Each block is size 'blocksize'
	//k may need adjusted
	printf("Total size of the .tar file: %d bytes\n",fs_tar_file);
	printf("Given a blocksize of %d bytes, k=%d\n",blocksize,k);
	printf("\n");

	//now check that k+m <= 2^w
	if(k+m > (1<<w)){
		fprintf(stderr,"Error: k+m (%d) is > 2^w (%d)\n\n",(k+m),(1<<w));
		exit(1);
	}

	printf("Creating generator matrix...\n\n");
	gen = RS_Classic_Generator( k, m, w );
	if(gen == NULL){
		fprintf(stderr,"Error creating generator\n");
		exit(1);
	}
	slices = new JER_Slices( k + m, k, blocksize, 1, gen); 

	buffer = (unsigned char*) malloc( blocksize * m * sizeof(char) );
	buffer2 = (unsigned char*) malloc( blocksize * sizeof(char) );

	bzero(buffer, blocksize * m );
	for( i = 0; i < m; i++ )
	{
		slices->Ptrs[i+k] = buffer + i * blocksize;
	}

	/* Reset file to the beginning. Read in a block at a time and update
	   the coding blocks. */
	fseek( fp, 0, SEEK_SET );

	//for each file
	printf("Encoding...\n");
	for( i = 0, l = 0; i < files.size(); i++ ){
		cursize = tarred_sizes[i];
		printf("%5sFile %d/%d: %s\n","",i+1,(int)(files.size()),files[i].c_str());
		//encode all the blocks of this file
		while( cursize > 0 ){
			bzero( buffer2, blocksize );

			cursize -= fread( buffer2, 1, (blocksize < cursize) ? blocksize : cursize, fp );
			checksum.push_back( compute_checksum( buffer2, blocksize ) );

			//add encoding data to m coding blocks
			for( j = 0; j < m; j++ ){
				//j = destination coding block
				//l = source data block
				val = slices->G->M->Get( j, l );
				/* Do region xor if that is all that is needed, otherwise
				   perform region multiply for values of 8, 16, 32. 
				   Other values of w not yet supported. */
				if( val == 1 ){
					galois_region_xor( slices->Ptrs[j+k], buffer2, slices->Ptrs[j+k], blocksize);
				}else{
					switch( w )
					{
						case 8:
							galois_w08_region_multiply( buffer2, val, blocksize,
									slices->Ptrs[j+k], 1 );
							break;
						case 16:
							galois_w16_region_multiply( buffer2, val, blocksize,
									slices->Ptrs[j+k], 1 );
							break;
						case 32:
							galois_w32_region_multiply( buffer2, val, blocksize,
									slices->Ptrs[j+k], 1 );
							break;
					}
				}
			}
			l++;
		}
	}
	printf("\n");
	fclose( fp );
	
	//delete the temp.tar file
	sys_command = "rm -rf temp.tar";
	//system( sys_command.c_str() );

	//compute a checksum for each coding block
	for( i = k; i < m+k; i++ ){
		checksum.push_back( compute_checksum( slices->Ptrs[i], blocksize ) );
	}

	/* Write the coding blocks out to file as well as the checksums and we are done. */
	fp = fopen( dest, "w" );

	fprintf( fp, "%d %d %d reed-sol %d\n", k, m, w, blocksize );
	/* fwrite( checksums ); */

	//write all m coding blocks
	fwrite( buffer, blocksize, m, fp );
	for( i = 0; i < k + m; i++ )
	{
		//write all n checksums
		fprintf( fp, "%u ", checksum[i] );
	}
	fclose( fp );

	printf("Archiving complete\n");

	printf("\n");
}

unsigned int compute_checksum( unsigned char* buffer, unsigned int blocksize )
{
	unsigned int i, retval;

	retval = 0;
	for( i = 0; i < blocksize; i++ )
	{
		retval += buffer[i];
	}

	return retval;
}

vector<string> read_directory( string dir )
{
	vector<string> files;
	vector<string> tfiles;
	string s1, s2;
	DIR* dp;
	struct stat st_obj;
	struct dirent *dirp;
	int i;

	if ( ( dp = opendir( dir.c_str() ) ) == NULL )
	{
		fprintf( stderr, "Error: could not open directory %s\n", dir.c_str() );
		fprintf( stderr, "\n");
		exit(1);
	}

	while( ( dirp = readdir( dp ) ) != NULL )
	{
		if( strcmp( dirp->d_name, "." ) && strcmp( dirp->d_name, ".." ) )
		{
			s1 = string( dirp->d_name );
			s2 = dir + "/" + s1;
			if( stat( s2.c_str(), &st_obj ) )
				pexit( "Error: in calling stat" );
			
			files.push_back( s2 );
			if( S_ISDIR( st_obj.st_mode ) )
			{
				tfiles = read_directory( s2 );
				for( i = 0; i < tfiles.size(); i++ )
					files.push_back( tfiles[i] );
			}
		}
	}
	closedir( dp );
	return files;
}

int oct_to_int(char *oct){
	int i;

	sscanf(oct, "%o", &i);

	return i;
}
